/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package xml;

import authentication.EncryptionType;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;

import data.AccountData;
import data.email.AccountType;
import java.io.File;
import java.io.IOException;
import org.xml.sax.SAXException;

/**
 * Třída umožňuje ukládat a načítat informace o účtu z XML souboru.
 * @author Pavel
 */
public class AccountFileHandler {
    private final String NODE_ROOT = "root";
    private final String NODE_USER_ACCOUNT = "EmailAccount";
    private final String NODE_RECEIVER = "recv";
    private final String NODE_SENDER = "send";
    private final String NODE_SERVER = "server";
    private final String NODE_AUTHENTICATION = "auth";
    private final String NODE_LOGIN = "login";
    private final String NODE_PASSWORD = "password";
    
    private final String ATTR_USER_ACCOUNT_NAME = "accName";
    private final String ATTR_TYPE = "type";
    private final String ATTR_AUTHENTICATION_ENCRYPTION = "encr";
    
    
    private String fileName;    
    private String accountName;
    private AccountData receiverData = null;
    private AccountData senderData = null;
    
    public AccountFileHandler(String fileName) {
        this.fileName = fileName;
    }
    
    public AccountFileHandler(File f) {
        this.fileName = f.getName();
    }
    
    
    public void SaveData(AccountData receiverData, AccountData senderData, String accountName) 
            throws ParserConfigurationException, IOException, SAXException, 
            DOMException,TransformerException,TransformerFactoryConfigurationError {
        File f = new File(this.fileName);        
        Document document = null;
        Element rootElement = null;
        if (f.exists()) {
            if (f.canRead()) {
                // Load existing XML.
                document = openExistingDocument();
                rootElement = document.getDocumentElement();
                
                // In order to maintain the overwriting behaviour.
                RemoveExistingAccount(rootElement, accountName);
            }
            else {
                // TODO: throw exception?
            }
        }
        else {            
            document = createNewDocument();
            rootElement = createAndAppentRootElement(document);            
        }
        
        // All is prepared, store the account!
        Element emailUserAccuntElement = document.createElement(NODE_USER_ACCOUNT);
        emailUserAccuntElement.setAttribute(ATTR_USER_ACCOUNT_NAME, accountName);
        rootElement.appendChild(emailUserAccuntElement);
        
        Element receiverElement = createReceiverElement(receiverData, document);
        Element senderElement = createSenderElement(senderData, document);
        
        if (receiverElement != null && senderElement != null) {
            emailUserAccuntElement.appendChild(receiverElement);
            emailUserAccuntElement.appendChild(senderElement);
        }
        else {
            // TODO: throw exception
        }
        
        // Save to file
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(document);
        StreamResult result =  new StreamResult(f);
        transformer.transform(source, result);
        
    }
    
    public void LoadData(String accountName) 
            throws IOException, ParserConfigurationException, SAXException, Exception {
        this.accountName = accountName;
        
        File f = new File(this.fileName);
        if (f.exists() && f.canRead()) {
            AccountData receiver = new AccountData();
            AccountData sender = new AccountData();
            
            Document document = openExistingDocument();
            Element rootElement = document.getDocumentElement();
            
            Element userAccountElem = null;
            
            NodeList userAccounts = rootElement.getChildNodes();
            for (int i = 0; i < userAccounts.getLength(); i++) {
                Element tmpUserAccountElem = (Element)(userAccounts.item(i));
                
                // Found?
                if (tmpUserAccountElem.getAttribute(ATTR_USER_ACCOUNT_NAME).equals(accountName)) {
                    userAccountElem = tmpUserAccountElem;
                    break;
                }
            }
            
            if (userAccountElem != null) {
                Element recvElem = getChildElementByTag(userAccountElem, NODE_RECEIVER);
                if (recvElem != null) {
                    try {
                        ExtractData(receiver, recvElem, false);
                        this.receiverData = receiver;
                    }
                    catch (Exception e) {
                        throw e;
                    }
                } 
                else {
                    throw new Exception("No receiver found");
                }
                
                Element sendElem = getChildElementByTag(userAccountElem, NODE_SENDER);
                if (sendElem != null) {    
                    try {
                        ExtractData(sender, sendElem, true);
                        this.senderData = sender;
                    }
                    catch (Exception e) {
                        throw e;
                    }                    
                }
                else {
                    throw new Exception("No sender found");
                }
            }
            else {
                throw new Exception("Couldn't find specified user account.");
            }
        }
        else {
            throw new Exception("Couldn't find xml containing stored user accounts.");
        }
    }
    
    public AccountData GetSenderData() {
        return this.senderData;
    }
    
    public AccountData GetReceiverData() {
        return this.receiverData;
    }
    
    void ExtractData(AccountData data, Element fromElem, boolean senderRequired) throws Exception {
        AccountType type = AccountType.valueOf(fromElem.getAttribute(ATTR_TYPE));              
        String serverName = "";
        EncryptionType encryption = null;
        String login = null;
        String password = null;
        boolean hasAuth = false;
        if (type != null && senderRequired == AccountType.isSenderProtocol(type)) {             
            Element serverElem = getChildElementByTag(fromElem, NODE_SERVER);
            Element authElem = getChildElementByTag(fromElem, NODE_AUTHENTICATION);
            
            if (serverElem != null) {
                serverName = serverElem.getTextContent();                
            }
            
            if (authElem != null) {
                hasAuth = true;
                encryption = EncryptionType.valueOf(authElem.getAttribute(ATTR_AUTHENTICATION_ENCRYPTION));
                
                Element loginElem = getChildElementByTag(authElem, NODE_LOGIN);
                Element passwordElem = getChildElementByTag(authElem, NODE_PASSWORD);
                
                if (loginElem != null && passwordElem != null) {
                    login = loginElem.getTextContent();
                    password = passwordElem.getTextContent();
                }                
            }
                        
            if (!hasAuth && !senderRequired) {
                throw new Exception("Receiver account must contain authentication data.");                
            }
            else if (hasAuth) {
                if (encryption == null) {
                    throw new Exception("Malformed encryption type.");                
                }
                else if (login == null || login.isEmpty() || password == null || password.isEmpty()) {
                    throw new Exception("Malformed login or password data.");                
                }
            }
            else if (serverName == null || serverName.isEmpty()) {
                throw new Exception("Account must contain server name.");                
            }
            
            // No errors, store.
            data.setEncryption(encryption);
            data.setLogin(login);
            data.setPass(password);
            data.setServer(serverName);
            data.setType(type);                        
        }
        else {
            throw new Exception("Account type and protocol mismatch.");  
        }
        
    }
    
    Element getChildElementByTag(Element parent, String tagName) {
        Element recvElem = null;
        NodeList validChildren = parent.getElementsByTagName(tagName);
        if (validChildren != null && validChildren.getLength() == 1) {
            recvElem = (Element) (validChildren.item(0));
        }        
        return recvElem;
    }
    
    
    private Document openExistingDocument() throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilderFactory documentBuilderFactory = 
                                    DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder;   
        documentBuilder = documentBuilderFactory.newDocumentBuilder();

        Document document;        
        document = documentBuilder.parse(this.fileName);
        
        return document;
    }
    
    private Document createNewDocument() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = 
                                    DocumentBuilderFactory.newInstance();           
        
        DocumentBuilder documentBuilder = 
                                    documentBuilderFactory.newDocumentBuilder();        
        
        Document document = documentBuilder.newDocument();
        return document;
    }
    
    private Element createAndAppentRootElement(Document document) {
        String root = "root";
        Element rootElement = document.createElement(root);
        document.appendChild(rootElement); 
        return rootElement;
    }
    
    private void RemoveExistingAccount(Element rootElement, String userAccountName) {
        NodeList nodes = rootElement.getElementsByTagName(NODE_USER_ACCOUNT);
        for (int i = 0; i < nodes.getLength(); i++) {
            if (((Element)(nodes.item(i))).getAttribute(ATTR_USER_ACCOUNT_NAME).equals(userAccountName)) {
                rootElement.removeChild(nodes.item(i));
            }
        }
    }
    
    private Element createReceiverElement(AccountData receiverData, Document document) {
        Element recvElem = null;
        if (!AccountType.isSenderProtocol(receiverData.getType())) {           
            recvElem = document.createElement(NODE_RECEIVER);
            recvElem.setAttribute(ATTR_TYPE, receiverData.getType().toString());
            
            Element serverElem = createServerElement(receiverData, document);
            Element authenticationElem = createAuthenticationElement(receiverData, document);
            
            if (serverElem == null || authenticationElem == null) {
                // Receiving account cannot have empty authentication nor server data, error!
                recvElem = null;
            }
            else {
                recvElem.appendChild(serverElem);                                
                recvElem.appendChild(authenticationElem);
            }            
        }        
        return recvElem;
    }
    
    private Element createSenderElement(AccountData senderData, Document document) {
        Element recvElem = null;
        AccountType type = senderData.getType();
        if (AccountType.isSenderProtocol(type)) {           
            recvElem = document.createElement(NODE_SENDER);
            recvElem.setAttribute(ATTR_TYPE, type.toString());
            
            Element serverElem = createServerElement(senderData, document);
            Element authenticationElem = createAuthenticationElement(senderData, document);
            
            if (serverElem == null) {
                // Sending account cannot have empty server data, error!
                recvElem = null;
            }
            else {
                recvElem.appendChild(serverElem);  
                
                // Sender account doesn't have to contain authentication.
                if (authenticationElem != null) {
                    recvElem.appendChild(authenticationElem);
                }
            }            
        }        
        return recvElem;
    }
        
    private Element createServerElement(AccountData receiverData, Document document) {
        Element serverElem = document.createElement(NODE_SERVER);
        serverElem.setTextContent(receiverData.getServer());
        return serverElem;
    }
    
    /**
     * 
     * @param receiverData
     * @param document
     * @return null if no login or no password are specified, otherwise authentication data node.
     */
    private Element createAuthenticationElement(AccountData data, Document document) {
        Element authenticationElem = null;        
        Element loginElem = createLoginElement(data, document);
        Element passwordElem = createPasswordElement(data, document);
                
        // If neither is empty, create authentication node.
        if (!(loginElem == null || passwordElem == null)) {                        
            authenticationElem = document.createElement(NODE_AUTHENTICATION);
            authenticationElem.setAttribute(ATTR_AUTHENTICATION_ENCRYPTION, data.getEncryption().toString());
            authenticationElem.appendChild(loginElem);
            authenticationElem.appendChild(passwordElem);
        }                       
        return authenticationElem;
    }
    
    private Element createLoginElement(AccountData data, Document document) {
        Element loginElem = null;
        String login = data.getLogin();
        if (!(login == null || login.isEmpty())) {
            loginElem = document.createElement(NODE_LOGIN);
            loginElem.setTextContent(login);
        } 
        
        return loginElem;
    }
    
    private Element createPasswordElement(AccountData data, Document document) {
        Element passwordElem = null;
        String password = data.getPass();
        if (!(password == null || password.isEmpty())) {
            passwordElem = document.createElement(NODE_PASSWORD);
            passwordElem.setTextContent(password);
        } 
        
        return passwordElem;
    }
}
